use std::cell::Cell;
use std::rc::Rc;

pub struct COBSwRegister(Rc<Cell<bool>>);

impl Clone for COBSwRegister {
    fn clone(&self) -> Self {
        let value = self.get();
        Self(Rc::new(Cell::new(value)))
    }
}

impl COBSwRegister {
    pub fn new() -> Self {
        Self(Rc::new(Cell::new(false)))
    }

    pub fn get(&self) -> bool {
        self.0.get()
    }

    pub fn set(&self, val: bool) {
        self.0.set(val);
    }

    pub fn rc_clone(&self) -> Self {
        Self(self.0.clone())
    }
}

////////////////////////////////////////////////////////////////

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignalDirection {
    TrackToCOB,
    COBToTrack,
}

impl From<bool> for SignalDirection {
    fn from(value: bool) -> Self {
        if value {
            SignalDirection::COBToTrack
        } else {
            SignalDirection::TrackToCOB
        }
    }
}

impl From<SignalDirection> for bool {
    fn from(value: SignalDirection) -> Self {
        match value {
            SignalDirection::COBToTrack => true,
            SignalDirection::TrackToCOB => false,
        }
    }
}

impl Default for SignalDirection {
    fn default() -> Self {
        SignalDirection::TrackToCOB
    }
}

pub struct COBSelRegister(Rc<Cell<SignalDirection>>);

impl Clone for COBSelRegister {
    fn clone(&self) -> Self {
        let value = self.get();
        Self(Rc::new(Cell::new(value)))
    }
}

impl COBSelRegister {
    pub fn new() -> Self {
        Self(Rc::new(Cell::new(false.into())))
    }

    pub fn get(&self) -> SignalDirection {
        self.0.get()
    }

    pub fn set(&self, sig_dir: SignalDirection) {
        self.0.set(sig_dir);
    }

    pub fn set_track_to_cob(&self) {
        self.0.set(SignalDirection::TrackToCOB);
    }

    pub fn set_cob_to_track(&self) {
        self.0.set(SignalDirection::COBToTrack);
    }

    pub fn set_floating(&self) {
        self.0.set(SignalDirection::default());
    }

    pub fn rc_clone(&self) -> Self {
        Self(self.0.clone())
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_sw_reg_set_get() {
        let reg1 = COBSwRegister::new();
        let reg2 = reg1.rc_clone();

        reg2.set(true);
        assert_eq!(reg1.get(), true);

        reg1.set(false);
        assert_eq!(reg2.get(), false);

        assert_eq!(reg1.0.as_ptr(), reg2.0.as_ptr());
    }

    #[test]
    fn test_sw_reg_clone() {
        let reg1 = COBSwRegister::new();
        let reg2 = reg1.clone();

        reg2.set(true);
        assert_eq!(reg1.get(), false);

        reg1.set(true);
        assert_eq!(reg2.get(), true);

        assert_ne!(reg1.0.as_ptr(), reg2.0.as_ptr());
    }

    #[test]
    fn test_sel_reg_set_get() {
        let reg1 = COBSelRegister::new();
        let reg2 = reg1.rc_clone();

        reg2.set(SignalDirection::COBToTrack);
        assert_eq!(reg1.get(), SignalDirection::COBToTrack);

        reg1.set(SignalDirection::TrackToCOB);
        assert_eq!(reg2.get(), SignalDirection::TrackToCOB);

        assert_eq!(reg1.0.as_ptr(), reg2.0.as_ptr());
    }

    #[test]
    fn test_sel_reg_clone() {
        let reg1 = COBSelRegister::new();
        let reg2 = reg1.clone();

        reg2.set(SignalDirection::COBToTrack);
        assert_eq!(reg1.get(), SignalDirection::TrackToCOB);

        reg1.set(SignalDirection::TrackToCOB);
        assert_eq!(reg2.get(), SignalDirection::COBToTrack);

        assert_ne!(reg1.0.as_ptr(), reg2.0.as_ptr());
    }
}
